package x86;

import help.Helper;
import assem.Instr;
import assem.OPER;
import temp.Label;
import temp.Temp;
import tree.BINOP;
import tree.CALL;
import tree.CJUMP;
import tree.CONST;
import tree.ESEQ;
import tree.EXPSTM;
import tree.Exp;
import tree.JUMP;
import tree.LABEL;
import tree.MEM;
import tree.MOVE;
import tree.NAME;
import tree.Stm;
import tree.TEMP;
import util.List;

public class Codegen {
	Frame frame;

	public Codegen(Frame f) {
		frame = f;
	}

	private List<Instr> ilist = null;
	private List<Instr> last = null;

	private void emit(Instr inst) {
		if (last != null)
			last = last.tail = new List<Instr>(inst, null);
		else
			last = ilist = new List<Instr>(inst, null);
	}

	private void munchStm(Stm s) {
		if (s instanceof tree.LABEL) {
			munchLABEL((tree.LABEL) s);
		} else if (s instanceof tree.MOVE) {
			munchMOVE((tree.MOVE) s);
		} else if (s instanceof tree.EXPSTM) {
			munchEXPSTM((tree.EXPSTM) s);
		} else if (s instanceof tree.CJUMP) {
			munchCJUMP((tree.CJUMP) s);
		} else if (s instanceof tree.JUMP) {
			munchJUMP((tree.JUMP) s);
		}
	}

	private void munchLABEL(LABEL l) {
		emit(new assem.LABEL(l.label.toString() + ":\n", l.label));
	}

	private void munchMOVE(MOVE s) {
		if (s.dst instanceof tree.MEM) {
			munchMOVE((MEM) s.dst, s.src);
		} else {
			munchMOVE((TEMP) s.dst, s.src);
		}
	}

	private void munchMOVE(TEMP dst, Exp src) {
		Temp t2 = munchExp(src);

		emit(new OPER("mov `d0, `s0", new List<Temp>(dst.temp, null),
				new List<Temp>(t2, null)));
	}

	private void munchMOVE(MEM d, Exp s) {
		Temp t1 = munchExp(d.exp);
		Temp t2 = munchExp(s);

		emit(new OPER("mov [`s0], `s1", null, new List<Temp>(t1,
				new List<Temp>(t2, null))));
	}

	private void munchEXPSTM(EXPSTM s) {
		munchExp(s.exp);
	}

	private void munchCJUMP(CJUMP s) {
		munchCMP(s);
		String cmd = "";
		switch (s.op) {
		case CJUMP.EQ:
			cmd = "je";
			break;
		case CJUMP.NE:
			cmd = "jne";
			break;
		case CJUMP.LT:
			cmd = "jl";
			break;
		case CJUMP.LE:
			cmd = "jle";
			break;
		case CJUMP.GT:
			cmd = "jg";
			break;
		case CJUMP.GE:
			cmd = "jge";
			break;
		case CJUMP.ULT:
			cmd = "jb";
			break;
		case CJUMP.ULE:
			cmd = "jbe";
			break;
		case CJUMP.UGT:
			cmd = "ja";
			break;
		case CJUMP.UGE:
			cmd = "jae";
			break;
		}
		emit(new OPER(cmd + " `j0", null, null, new List<Label>(s.ifTrue,
				new List<Label>(s.ifFalse, null))));
	}

	private void munchCMP(CJUMP s) {
		Temp t1 = munchExp(s.left);
		Temp t2 = munchExp(s.right);
		emit(new OPER("cmp `s0, `s1", null, new List<Temp>(t1, new List<Temp>(
				t2, null))));
	}
	
	private void munchJUMP(JUMP s) {
		if (s.exp instanceof NAME) {
			NAME n = (NAME) s.exp;
			emit(new OPER("jmp `j0", null, null, new List<Label>(n.label, null)));
		} else {
			Temp t = munchExp(s.exp);
			emit(new OPER("jmp `s0", null, new List<Temp>(t, null), s.targets));
		}
	}

	private Temp munchExp(Exp s) {
		if (s instanceof BINOP) {
			return munchBINOP((BINOP) s);
		} else if (s instanceof CALL) {
			return munchCALL((CALL) s);
		} else if (s instanceof CONST) {
			return munchCONST((CONST) s);
		} else if (s instanceof ESEQ) {
			return munchESEQ((ESEQ) s);
		} else if (s instanceof MEM) {
			return munchMEM((MEM) s);
		} else if (s instanceof NAME) {
			return munchNAME((NAME) s);
		} else if (s instanceof TEMP) {
			return munchTEMP((TEMP) s);
		}
		return null;
	}

	private Temp munchBINOP(BINOP s) {
		Temp tleft = munchExp(s.left);
		Temp tright = munchExp(s.right);
		Temp tret = new Temp();
		Temp teax = new Temp();
		Temp tedx = new Temp();

		switch (s.binop) {
		case BINOP.PLUS:
			emit(new assem.MOVE("mov `d0, `s0", tret, tleft));
			emit(new OPER("add `d0, `s1", new List<Temp>(tret, null),
					new List<Temp>(tret, new List<Temp>(tright, null))));
			break;
		case BINOP.MINUS:
			emit(new assem.MOVE("mov `d0, `s0", tret, tleft));
			emit(new OPER("sub `d0, `s1", new List<Temp>(tret, null),
					new List<Temp>(tret, new List<Temp>(tright, null))));
			break;
		case BINOP.TIMES:
			emit(new assem.MOVE("mov `d0, eax", teax, Frame.eax));
			emit(new assem.MOVE("mov `d0, edx", tedx, Frame.edx));
			emit(new assem.MOVE("mov eax, `s0", Frame.eax, tleft));
			emit(new OPER("imul `s1", new List<Temp>(Frame.eax, null),
					new List<Temp>(Frame.eax, new List<Temp>(tright, null))));
			emit(new assem.MOVE("mov `d0, eax", tret, Frame.eax));
			emit(new assem.MOVE("mov eax, `s0", Frame.eax, teax));
			emit(new assem.MOVE("mov edx, `s0", Frame.edx, tedx));
			break;
		case BINOP.LSHIFT:
			emit(new assem.MOVE("mov `d0, `s0", tret, tleft));
			emit(new OPER("shl `d0, " + ((CONST) s.right).value,
					new List<Temp>(tret, null), new List<Temp>(tret,
							new List<Temp>(tright, null))));
			break;
		default:
			throw new Error("oops: Binop");
		}
		return tret;
	}

	private Temp munchCALL(CALL s) {
		int size = s.args.size() * 4;

		//inverte lista de parametros para empilha-los
		List<Exp> inv = Helper.revertsExpList(s.args);

		//empilha parametros
		for (List<Exp> args = inv; args != null; args = args.tail) {
			if (args.head instanceof CONST) {
				emit(new OPER("push " + ((CONST) args.head).value, null, null));
			} else {
				Temp t = munchExp(args.head);
				emit(new OPER("push `s0", null, new List<Temp>(t, null)));
			}
		}

		if (s.func instanceof NAME) {
			emit(new assem.OPER("call " + ((NAME) s.func).label,
					Frame.calldefs, null));
		} else {
			Temp t = munchExp(s.func);
			emit(new assem.OPER("call `s0", Frame.calldefs, new List<Temp>(t,
					null)));
		}

		//restaura posicao da pilha pilha
		if (size != 0) {
			emit(new assem.OPER("add esp, " + size, null, null));
		}

		return frame.RV();
	}

	private Temp munchCONST(CONST s) {
		Temp t = new Temp();
		emit(new OPER("mov `d0, " + s.value, new List<Temp>(t, null), null));
		return t;
	}

	private Temp munchESEQ(ESEQ s) {
		munchStm(s.stm);
		Temp t1 = munchExp(s.exp);
		return t1;
	}

	private Temp munchMEM(MEM s) {
		Temp dst = new Temp();
		Temp src = munchExp(s.exp);
		emit(new OPER("mov `d0, [`s0]", new List<Temp>(dst, null),
				new List<Temp>(src, null)));
		return dst;
	}

	private Temp munchNAME(NAME s) {
		Temp t = new Temp();
		emit(new OPER("mov `d0, " + s.label, new List<Temp>(t, null), null));
		return t;
	}

	private Temp munchTEMP(TEMP s) {
		return s.temp;
	}

	/*-------------------------------------------------------------*
	 *                              MAIN                           *
	 *-------------------------------------------------------------*/
	List<Instr> codegen(Stm s) {
		List<Instr> l;
		munchStm(s);
		l = ilist;
		ilist = last = null;
		return l;
	}

	List<Instr> codegen(List<Stm> body) {
		List<Instr> l = null, t = null;

		for (; body != null; body = body.tail) {
			munchStm(body.head);
			if (l == null)
				l = ilist;
			else
				t.tail = ilist;
			t = last;
			ilist = last = null;
		}
		return l;
	}
}
